Kitchen coding nightmares: JavaScript scope
Lately at the Recurse Center I’ve been developing a JavaScript client for my Settlers game. As a Perl developer working with JavaScript, it has been a fun experience. JavaScript feels very perly - both share a flexible syntax, first class functions and objects as hashes. And both languages have a lax interpreter which should have been put in strict mode in the first place (ha-ha!). One way in which JavaScript is very different from Perl is its scoping rules. I was burned by these more than once, and so if you’re new to JavaScript, you might find the following summary and recommendations useful.
Functional scoping
Variables are declared with the var
keyword:
var name = "David";
Variables are functionally-scoped, which means that if declared within a function, the variable is private to the function block. Variables declared outside of functions are globally scoped. And there is no other type of block scoping (such as within if-else or for loops).
var name = "David";
function log_name (name) // private
{
console.log(name);
}
var names = ["Jen", "Jim", "Jem", "Jon"];
for (var i = 0; i < names.length; i++)
{
var name = names[i]; // overwriting the global
}
console.log(name); // Jon NOT David
Functions as variables
Function names are stored as variables under the same scoping rules as ordinary variables. There are two ways to declare functions:
function log_name () { }
and:
var log_name = function () { }
Both of these are the same. Which means it’s possible to inadvertently overwrite a function with another variable declaration:
function name () { return "David"; }
var name = "John";
name(); // error, name is not a function anymore
Hoisting
JavaScript interpreters have a initial-runtime phase, (similar to Perl’s BEGIN
), where all variable declarations are executed before other code. This is known as “hoisting”, but practically what it means is that you can use a variable before you declare it!
console.log(name); // yep, this works
var name = "David";
Bind
JavaScript makes heavy use of anonymous functions and callbacks.To modify the scope of a function, JavaScript1 provides bind. This is easier to understand by example. If I have a point object and I want a method to draw it to the canvas, by loading an image:
Point.prototype.draw = function()
{
var ctx = get_canvas_context(); // declared elsewhere
var img = new Image();
img.onload = function () { // anonymous function
ctx.drawImage(img, this.x, this.y);
}.bind(this);
img.src = "/point.png";
}
Here I use bind
to inject the point object’s scope into the anonymous function. Otherwise I wouldn’t be able to access the x
and y
properties of the point as this
would be referencing something else.
For a more thorough explanation of JavaScript scope, I recommend Todd Motto’s article, Everything you wanted to know about JavaScript scope.
Coping with scoping
OK so that was the bad news; the good news is there are plenty of techniques for handling JavaScript’s scoping rules. Depending on the context you may find some or all of these methods useful.
Naming conventions
The first thing you can do to avoid clashes is adopt a naming convention. For example, name all functions with verb-noun constructs (like “get_address”) and all value variables with plain nouns (like “addresses”). This is not a complete solution, but at a minimum it will reduce the chances of a function being replaced by a value variable.
One var per scope
Another technique for managing variable scope is to only allow one var
statement per scope. So a typical program might look like this:
// declare global scope variables
var foo = "/root/assets",
bar = 0;
function execute (foo)
{
var i, j, bar; // functional scope
for (i = 0; i < foo.length; i++)
{
for (j = 0; j < foo.length; j++)
{
// do something
}
}
}
Use strict
This is a convention all Perl programmers should be comfortable with. Enable strict mode in JavaScript. Just like with Perl, JavaScript’s strict mode can catch several cases of variable-related bugs. Enable it globally with:
"use strict";
Generally JavaScript experts recommend using a functionally-scoped version of strict - in this case the declaration is placed inside a function block. This is useful to prevent script concatenation errors (where an imported script does not satisfy the strict rules).
(function () {
"use strict";
// this function is strict...
}());
Objects as namespaces
If you were thinking a simple way to solve all of the namespace clashes was with modules, allow me to be the first to tell you that JavaScript has no notion of modules (being a prototyped language). There is no import
keyword. In HTML any code that is loaded with a script
tag is simply concatenated to the current scope.
There are solutions to this limitation though. In JavaScript the Definitive Guide, author David Flanagan proposes using objects as namespaces (sixth edition, section 9.9.1). Each object’s scope can be used to encapsulate the behavior and data specific to that domain. For example:
// everything is scoped to point.*
var point = {};
point.Point = function (_x, _y) {
this.x = _x;
this.y = _y
}
point.Point.prototype.coordinates = function ()
{
return [this.x, this.y];
}
// now lets try it out ...
var p = new point.Point(1,3);
console.log(p.coordinates());
To package code as modules, there is the module pattern. Finally although JavaScript has no native import method, there are several external libraries that can provide that behavior, like RequireJS.
Let
The next major version of JavaScript, ES6 provides let. This keyword provides block-level scoping of variables, similar to other mainstream languages. ES6 is not supported everywhere yet, but you can use a transpiler like Babel to convert ES6 JavaScript back to ES5.
Use a code linter
Browsers do not throw enough exceptions when processing JavaScript. Instead they try to soldier on and do what the programmer meant rather than what they typed. This is good2 for users as they get a uninterrupted browsing experience, but for us programmers this is definitely a bad thing™. Browser robustness makes JavaScript difficult to debug, and which is where a code linter steps in - it analyzes code and reports any errors or warnings they find. For JavaScript I like JSHint.
1 Introduced in ES5 JavaScript - which is supported by all modern browsers. For solutions for older JavaScript versions, use call
or apply
.
2 It’s probably a bad thing for users too - the overhead in processing syntactically wrong code degrades performance and worse, encourages more incorrect code to be written.
This article was originally posted on PerlTricks.com.
Tags
David Farrell
David is a professional programmer who regularly tweets and blogs about code and the art of programming.
Browse their articles
Feedback
Something wrong with this article? Help us out by opening an issue or pull request on GitHub